package edu.jhu.secondhome.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import edu.jhu.secondhome.database.accessors.ReservationAccessor;
import edu.jhu.secondhome.database.accessors.RoomAccessor;
import edu.jhu.secondhome.database.accessors.UserAccessor;
import edu.jhu.secondhome.database.beans.Reservation;
import edu.jhu.secondhome.database.beans.Room;
import edu.jhu.secondhome.database.beans.User;

public class ControllerServlet extends HttpServlet
{
  /**
   * 
   */
  private static final long serialVersionUID = 6865293442603761077L;

  public void init() throws ServletException
  {

  }

  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException
      {
    processRequest(req, resp);
      }

  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException
      {
    processRequest(req, resp);
      }

  public void processRequest(HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException
      {
    response.setContentType("text/html;charset=UTF-8");
    ServletContext servletContext = getServletContext();
    HttpSession session = request.getSession();

    User user = null;
    Object userAttr = session.getAttribute("user");
    if ( userAttr instanceof User )
      user = (User) userAttr;

    String source = request.getParameter("source");
    if ("login".equals(source))
    {
      if ( user != null )
      {
        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/index.jsp");
        dispatcher.forward(request, response);
        return;
      }

      RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/login");
      dispatcher.forward(request, response);
      return;
    }
    else if ("logout".equals(source))
    {
      session.invalidate();
      // Forward the request to index.jsp
      RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/index.jsp");
      dispatcher.forward(request, response);
      return;
    }
    else if ("register".equals(source))
    {
      // Forward the request to index.jsp
      RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/registration");
      dispatcher.forward(request, response);
      return;
    }
    else if ("createRes".equals(source))
    {
      if (user != null)
      {
        // Forward the request to index.jsp
        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/index.jsp");
        dispatcher.forward(request, response);
        return;
      }
    }
    else if ("viewRes".equals(source))
    {	
      if (user != null) {
        loadManageReservations(request, response, servletContext, user);
        return;
      } else {
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("deleteRes".equals(source))
    {
      if (user != null) {
        final int id = Integer.parseInt(request.getParameter("id"), 10);
        ReservationAccessor reservDB = new ReservationAccessor();

        try {
          if (reservationEmailMatches(id, user) || user.isAdmin()) {
            log("user " + session.getAttribute("user") + 
                " attempting to delete reservation id=" + id);
            reservDB.deleteReservation(id);
          } else {
            // unauthorized - not reservation for that user (shouldn't happen through gui, but if it does...) or not admin
            loadUnauthorizedPage(request, response, servletContext, "delete");
            return;
          }
        } catch (SQLException e) {
          log("Could not delete the reservation with id=" + id + " by user=" + user, e);
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem deleting the reservation you requested.");
          return;
        }
        loadManageReservations(request, response, servletContext, user);
        return;
      } else {
        // user was null, load login page
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("editRes".equals(source))
    {	
      if (user != null) {
        final int id = Integer.parseInt(request.getParameter("id"), 10);
        Reservation reservEdit = null;
        List<Room> availableRooms = null;
        try {
          ReservationAccessor reservDB = new ReservationAccessor();
          reservEdit = reservDB.getReservationById(id);
        } catch (SQLException e) {
          log("Could not retrieve reservation with id=" + id);
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem locating the reservation you requested.");
          return;
        }
        if (reservEdit == null) {
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem locating the reservation you requested.");
          return;
        }
        try {
          if (!user.isAdmin() && !reservationEmailMatches(id, user)) {
            // unauthorized - not reservation for that user (shouldn't happen through gui, but if it does...) or not admin
            loadUnauthorizedPage(request, response, servletContext, "edit");
            return;
          }
        } catch (SQLException e) {
          log("Could not retrieve the reservation email information for id=" + id + " by user=" + user, e);
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem locating the reservation you requested.");
          return;
        }
        try {
          RoomAccessor roomDB = new RoomAccessor();
          availableRooms = roomDB.getRoomsForHotelWithCapacityFreeOnDate(
              reservEdit.getHotelName(), 
              reservEdit.getNumOfPeople(), 
              reservEdit.getStartDate(), 
              reservEdit.getNumOfDays());
          if (availableRooms == null) {
            // no other rooms available, and no error caught; make empty list to prevent NPE
            availableRooms = Collections.emptyList();
          }
        } catch (SQLException e) {
          log("Could not retrieve available rooms where hotel=" + 
              reservEdit.getHotelName() + " numOfPeople=" +
              reservEdit.getNumOfPeople() + " startDate=" + 
              reservEdit.getStartDate() + " numOfDays=" + 
              reservEdit.getNumOfDays(), e);
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem loading available rooms in the hotel for the time period you requested.");
          return;
        }

        if (availableRooms != null) {
          request.setAttribute("reservEdit", reservEdit);
          request.setAttribute("availableRooms", availableRooms);
          RequestDispatcher dispatcher = servletContext
              .getRequestDispatcher("/manage/forms/editReservation.jsp");
          dispatcher.forward(request, response);
          return;
        } else {
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem loading available rooms in the hotel for the time period you requested.");
          return;
        }
      } else {
        // user was null, load login page
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("updateRes".equals(source))
    {	
      if (user != null) {
        final int id = Integer.parseInt(request.getParameter("id"), 10);
        String room = request.getParameter("room");
        room = room.split("::")[1]; // the values here are costPerNight::roomNum
        final String newCost = request.getParameter("totalCost");

        try {
          if (!user.isAdmin() && !reservationEmailMatches(id, user)) {
            // unauthorized - not reservation for that user (shouldn't happen through gui, but if it does...) or not admin
            loadUnauthorizedPage(request, response, servletContext, "update");
            return;
          }
        } catch (SQLException e) {
          log("Could not retrieve the reservation email information for id=" + id + " by user=" + user, e);
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem locating the reservation you requested.");
          return;
        }

        try {
          ReservationAccessor reservDB = new ReservationAccessor();
          reservDB.updateReservation(id, room, Integer.parseInt(newCost, 10));
        } catch (SQLException e) {
          log("Could not update reservation with id=" + id + " with room=" + room + " and newCost=" + newCost, e);
          // error - mysql error
          loadErrorPage(request, response, servletContext, "There was a problem updating the reservation you requested.");
          return;
        }
        loadManageReservations(request, response, servletContext, user);
      } else {
        // user was null, load login page
        loadLoginPage(request, response, servletContext);
        return;
      }
      return;
    }
    else if ("acctManagement".equals(source))
    {
      if (user != null)
      {
        // Forward the request to manageAccount.jsp
        loadManageAccount( request, response, servletContext, user );
        return;
      }
      else
      {
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("updateBillingInfo".equals(source))
    {
      if (user != null)
      {
        final UserAccessor ua = new UserAccessor();
        String message = "";

        String cardNum = request.getParameter("billingCardNum");
        cardNum = cardNum == null ? "" : cardNum;

        if (!cardNum.isEmpty() && cardNum.contains("*")) {
          // if card number still contains stars, use old card number in statement:
          log("Card number has asterisks present; trying to use old card " +
              "for user " + user.getEmail());
          message = "Card number has asterisks present; keeping old card.<br />";
          try {
            cardNum = ua.getUserByEmail(user.getEmail()).getCreditCardNum();
          } catch (SQLException e) {
            // we don't want to save a starry card number.
            // if we can't get the old one, remove it:
            cardNum = "";
            message = "Card number has asterisks present; couldn't retrieve " + 
                "old card, so instead it will be removed.<br />";
            log("Failed to get old card number value for user " + 
                user.getEmail() + "; removing card number instead.");
          }
        }

        String cardExpMonth = request.getParameter("billingExpMonth");
        cardExpMonth = cardExpMonth == null ? "" : cardExpMonth;
        String cardExpYear = request.getParameter("billingExpYear");
        cardExpYear = cardExpYear == null ? "" : cardExpYear;
        String cardCode = request.getParameter("billingCardCode");
        cardCode = cardCode == null ? "" : cardCode;
        String address1 = request.getParameter( "billingAddr1" );
        address1 = address1 == null ? "" : address1;
        String address2 = request.getParameter( "billingAddr2" );
        address2 = address2 == null ? "" : address2;
        String address3 = request.getParameter( "billingAddr3" );
        address3 = address3 == null ? "" : address3;
        String city = request.getParameter( "billingCity" );
        city = city == null ? "" : city;
        String state = request.getParameter( "billingState" );
        state = state == null ? "" : state;
        String zip = request.getParameter( "billingZip" );
        zip = zip == null ? "" : zip;

        String fullBillingAddr = User.createAddressString(address1, address2, address3, city, state, zip);

        try {
          ua.updateUserBillingInfo(user.getEmail(), fullBillingAddr, cardNum, cardExpMonth, cardExpYear, cardCode);
          request.setAttribute("message", message + "Payment Information Updated Successfully.");
        } catch (SQLException e) {
          log("Couldn't update billing information for " + user.getEmail() +  ".", e);
          List<String> error = new ArrayList<String>();
          error.add("Couldn't update billing information for " + user.getEmail() + ".");
          request.setAttribute("errors", error);
        }

        loadManageAccount(request, response, servletContext, user);
        return;
      }
      else
      {
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("updateAcct".equals(source))
    {
      if (user != null)
      {
        //        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/updateAccount");
        //        dispatcher.forward(request, response);
        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/updateAccount");
        dispatcher.forward(request, response);
        return;
      }
      else
      {
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ( "changePassword".equals( source ) )
    {
      if (user != null)
      {
        // Forward the request to manageAccount.jsp
        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/changePassword");
        dispatcher.forward(request, response);
        return;
      }
      else
      {
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("grantAdminPrivileges".equals(source)) {
      if (user != null) {
        if (user.isAdmin()) {
          final UserAccessor ua = new UserAccessor();
          String email = request.getParameter("userToGrant");
          User grant = null;
          try {
            grant = ua.getUserByEmail(email);
          } catch(SQLException e) {
            // should never happen, since the email addrs are served up right from the db...
            log("Couldn't grant admin privileges to " + email +  ".  User doesn't exist.", e);
            List<String> error = new ArrayList<String>();
            error.add("Could not grant admin privileges to " + email + ".  User doesn't exist.");
            request.setAttribute("errors", error);
          }
          try {
            if (grant != null) {
              ua.editAdminPriviledges(email, true);
              request.setAttribute("message", "Privileges Granted Successfully.");
            }
          } catch (SQLException e) {
            log("Couldn't grant admin privileges to " + email +  ".", e);
            List<String> error = new ArrayList<String>();
            error.add("Could not grant admin privileges to " + email + ".");
            request.setAttribute("errors", error);
          }
        } else {
          request.setAttribute("message", "You do not have sufficient privileges to perform this action.");
        }

        // Forward the request to manageAccount.jsp
        loadManageAccount(request, response, servletContext, user);
        return;
      } else {
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("revokeAdminPrivileges".equals(source)) {
      if (user != null) {
        if (user.isAdmin()) {
            final UserAccessor ua = new UserAccessor();
            String email = request.getParameter("userToRevoke");
            User grant = null;
            try {
              grant = ua.getUserByEmail(email);
            } catch(SQLException e) {
              // should never happen, since the email addrs are served up right from the db...
              log("Couldn't revoke admin privileges from " + email +  ".  User doesn't exist.", e);
              List<String> error = new ArrayList<String>();
              error.add("Could not revoke admin privileges from " + email + ".  User doesn't exist.");
              request.setAttribute("errors", error);
            }
            try {
              if (grant != null) {
                ua.editAdminPriviledges(email, false);
                request.setAttribute("message", "Privileges Revoked Successfully.");
              }
            } catch (SQLException e) {
              log("Couldn't revoke admin privileges from " + email +  ".", e);
              List<String> error = new ArrayList<String>();
              error.add("Could not revoke admin privileges from " + email + ".");
              request.setAttribute("errors", error);
            }
        } else {
          request.setAttribute("message", "You do not have sufficient privileges to perform this action.");
        }
        // Forward the request to manageAccount.jsp
        loadManageAccount(request, response, servletContext, user);
        return;
      } else {
        loadLoginPage(request, response, servletContext);
        return;
      }
    }
    else if ("resSearch".equals(source))
    {
      // Forward the request to reservationSearch servlet
      RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/reservationSearch");
      dispatcher.forward(request, response);
      return;
    }
    else if ("viewRoomAvailability".equals(source))
    {
      // Forward the request to availableRooms servlet
      RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/availableRooms");
      dispatcher.forward(request, response);
      return;
    }
    else if ("hotelInfo".equals(source))
    {
      if ( user == null )
      {
        loadLoginPage( request, response, servletContext );
        return;
      }
      else
      {
        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/search/checkout.jsp");
        dispatcher.forward(request, response);
        return;
      }
    }
    else if ("confirmCheckout".equals(source))
    {
      if ( user != null )
      {
        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/reservationCreation");
        dispatcher.forward(request, response);
        return;
      }
      else
      {
        loadLoginPage( request, response, servletContext );
        return;
      }
    }

    // Forward the request to register.jsp
    RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/index.jsp");
    dispatcher.forward(request, response);
      }


  private void loadManageReservations(HttpServletRequest request, 
      HttpServletResponse response, ServletContext sc, final User user) throws ServletException, IOException {
    ReservationAccessor reservDB = new ReservationAccessor();
    List<Reservation> reservations = null;
    try {
      if (user.isAdmin())
        reservations = reservDB.getReservations();
      else 
        reservations = reservDB.getReservationsForUser(user.getEmail());
    } catch (SQLException e) {
      reservations = Collections.emptyList();
      log(e.getMessage());
      log(e.getSQLState());
      StringWriter sw = new StringWriter();
      e.printStackTrace(new PrintWriter(sw));
      log(sw.toString());
    }

    request.setAttribute("reservations", reservations == null ? Collections.emptyList() : reservations);

    final Calendar now = Calendar.getInstance();
    now.set(Calendar.HOUR_OF_DAY, 0);
    now.set(Calendar.MINUTE, 0);
    now.set(Calendar.SECOND, 0);
    now.set(Calendar.MILLISECOND, 0);
    request.setAttribute("today", now.getTime()); // calendar.getTime() returns Date object.

    // Forward the request to manageReservations.jsp
    RequestDispatcher dispatcher = sc.getRequestDispatcher("/manage/manageReservations.jsp");
    dispatcher.forward(request, response);
  }

  private boolean reservationEmailMatches(final int id, final User user) throws SQLException {
    try {
      ReservationAccessor reservDB = new ReservationAccessor();
      final String resEmail = reservDB.getReservationEmailById(id);
      if (resEmail == null)
        throw new SQLException("Could not retrieve an email address for reservation with id=" + id + " by user=" + user);
      else {
        return resEmail.equals(user.getEmail());
      }
    } catch (SQLException e) {
      log("Could not retrieve an email address for reservation with id=" + id + " by user=" + user, e);
      throw e;
    }
  }

  private void loadLoginPage(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) throws ServletException, IOException {  
    RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/login.jsp");
    dispatcher.forward(request, response);
  }

  private void loadUnauthorizedPage(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, final String function) throws ServletException, IOException {
    RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/manage/errorPages/unauthorized.jsp");
    request.setAttribute("function", function);
    dispatcher.forward(request, response);
  }

  private void loadErrorPage(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, final String error) throws ServletException, IOException {
    RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/manage/errorPages/error.jsp");
    request.setAttribute("error", error);
    dispatcher.forward(request, response);
  }

  private void loadManageAccount( HttpServletRequest request,
      HttpServletResponse response, ServletContext servletContext,
      final User user ) throws ServletException, IOException
  {
    UserAccessor ua = new UserAccessor();
    loadAndStoreDatabaseUser( request, response, servletContext, user, ua );
    
    if ( user.isAdmin() )
    {
      List<String> admins;
      try
      {
        admins = ua.getUserEmailsAdminStatus( true );
      } catch ( SQLException e )
      {
        log( "Couldn't retrieve admin users for account management.", e );
        admins = Collections.emptyList();
      }
      List<String> users;
      try
      {
        users = ua.getUserEmailsAdminStatus( false );
      } catch ( SQLException e )
      {
        log( "Couldn't retrieve non-admin users for account management.", e );
        users = Collections.emptyList();
      }

      if ( admins == null )
        admins = Collections.emptyList();
      if ( users == null )
        users = Collections.emptyList();
      request.setAttribute( "admins", admins );
      request.setAttribute( "users", users );
    }
    // Forward the request to manageAccount.jsp
    RequestDispatcher dispatcher = servletContext
        .getRequestDispatcher( "/manageAccount.jsp" );
    dispatcher.forward( request, response );
  }
  
  protected void loadAndStoreDatabaseUser( HttpServletRequest request,
      HttpServletResponse response, ServletContext servletContext, User user,
      UserAccessor ua )
  {
    User dbUser;
    try
    {
      dbUser = ua.getUserByEmail( user.getEmail() );
    } catch ( SQLException e )
    {
      // this should never happen since it's the currently logged in user.
      log( "Couldn't retrieve user data for email " + user.getEmail(), e );
      dbUser = user;
    }
    String cardNum = dbUser.getCreditCardNum();
    dbUser.setPassword( "" );
    dbUser.setCreditCardSecCode( "" );
    if ( cardNum != null && !cardNum.isEmpty() )
    {
      final int length = cardNum.length();
      if ( length > 4 )
      {
        dbUser.setCreditCardNum( cardNum.substring( 0, ( length - 4 ) )
            .replaceAll( "\\d", "*" ) + cardNum.substring( length - 4 ) );
      }
    }
    request.setAttribute( "dbUser", dbUser );   
  }
}
